package staff.logic;

import static staff.util.DateUtils.datetoString;
import static staff.util.DateUtils.getDateFormat;
import static staff.util.DateUtils.stringToDate;

import java.util.Comparator;
import java.util.Date;
import java.util.List;

/** Employee class */
public class Employee implements Comparable<Employee> {
    /** Counter for employe id generate */
    private static int counter = 0;

    /** Employe data fields */
    private String fullName;
    private Date birthDate;
    private Date hireDate;
    // private String description; - this field was moved to Position class
    private int employeId;
    private int managerId;
    /** Position is state of employe. State pattern used here */
    private Position position;

    /** Tags for employe fields. Used by employe loaders and savers. */
    public static enum FieldTag {
        fullName, birthDate, hireDate, position, description, employeId, managerId;

        public static boolean containsName(String name) {
            boolean result = false;
            for (FieldTag tag : values()) {
                if (tag.name().equals(name)) {
                    result = true;
                    break;
                }
            }
            return result;
        }
    }

    /** Tags for employe positions */
    public static enum PositionType {
        WORKER,
        MANAGER,
        OTHER
    }

    /** Contsructor */
    public Employee() {
        employeId = ++counter;
        setAsWorker(); // new employe is worker by default
    }

    /** Employes equals if have same employe id */
    @Override
    public boolean equals(Object obj) {
        boolean result = false;
        if (obj instanceof Employee) {
            Employee other = (Employee) obj;
            result = (this.employeId == other.employeId);
        }
        return result;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 41 * hash + this.employeId;
        return hash;
    }

    /** Default sort order by employe id */
    public int compareTo(Employee other) {
        return (employeId == other.getEmployeId()) ? 0 : (employeId < other.getEmployeId() ? 1 : -1);
    }

    /** Comparator that orders employe by full name */
    public static final Comparator<Employee> FULL_NAME_ORDER = new FullNameOrder();

    private static class FullNameOrder implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            return e1.getFullName().compareToIgnoreCase(e2.getFullName());
        }
    }

    /** Comparator that orders employe by hire date */
    public static final Comparator<Employee> HIRE_DATE_ORDER = new HireDateOrder();

    private static class HireDateOrder implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            return e1.getHireDate().compareTo(e2.getHireDate());
        }
    }

    /* Methods for change employe position: */

    public void setAsWorker() {
        position = new Worker(this);
    }

    public void setAsManager() {
        position = new Manager(this);
    }

    public void setAsOther(String description) {
        position = new Other(this);
        setDescription(description);
    }

    public void setPosition(String position) {
        PositionType positionType = PositionType.valueOf(position);
        switch (positionType) {
        case WORKER:
            setAsWorker();
            break;
        case MANAGER:
            setAsManager();
            break;
        case OTHER:
            setAsOther("");
            break;
        default:
            throw new IllegalStateException("Position " + positionType + " not found in case statement.");
        }
    }

    /* Getter and setter methods: */

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getFullName() {
        return fullName;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

    public Date getHireDate() {
        return hireDate;
    }

    public void setEmployeId(int id) {
        this.employeId = id;
        if (id > counter) {
            counter = id; // ensure counter has proper value after all
        }
    }

    public Integer getEmployeId() {
        return employeId;
    }

    public Integer getManagerId() {
        return managerId;
    }

    public void setManagerId(Integer managerId) {
        this.managerId = managerId;
    }

    /* Delegate some behaviour to position variable, according to state pattern: */

    public Employee getManager(EmployeSystem employeSystem) {
        return position.getManager(employeSystem);
    }

    public List<Employee> getSubordinate(EmployeSystem employeSystem) {
        return position.getSubordinate(employeSystem);
    }

    public PositionType getPosition() {
        return position.getType();
    }

    public String getDescription() {
        return position.getDescription();
    }

    public void setDescription(String description) {
        position.setDescription(description);
    }

    /* Parameters and methods for display employe */

    /** Header for employe toString */
    public static final String EMPLOYE_HEADER =
                    "ID      Full Name                      Birthday   Hire date  Position Description Manager ID\n"
                                    + "======= ============================== ========== ========== ======== =========== ==========";

    // format for employe header
    private static final String EMPLOYE_PRINTF_FORMAT =
                    "%1$7d %2$-30.30s %3$-10.10s %4$-10.10s %5$-8.8s %6$-11.11s %7$10d";

    @Override
    public String toString() {
        String birthDateString = datetoString(getBirthDate(), getDateFormat());
        String hireDateString = datetoString(getHireDate(), getDateFormat());
        String result =
                        String.format(EMPLOYE_PRINTF_FORMAT, getEmployeId(), getFullName(), birthDateString,
                                        hireDateString, getPosition(), getDescription(), getManagerId());
        return result;
    }

    /* Methods for set and get parameters by string values */

    /** Set employe param by name */
    public void setParamByName(String paramName, String paramValue) {
        FieldTag fieldTag = FieldTag.valueOf(paramName);
        switch (fieldTag) {
        case fullName:
            setFullName(paramValue);
            break;
        case birthDate:
            Date bDate = stringToDate(paramValue, getDateFormat());
            setBirthDate(bDate);
            break;
        case hireDate:
            Date hDate = stringToDate(paramValue, getDateFormat());
            setHireDate(hDate);
            break;
        case position:
            setPosition(paramValue);
            break;
        case description:
            setDescription(paramValue);
            break;
        case employeId:
            int empId = Integer.parseInt(paramValue);
            setEmployeId(empId);
            break;
        case managerId:
            int manId = Integer.parseInt(paramValue);
            setManagerId(manId);
            break;
        default:
            throw new IllegalStateException("Field tag " + fieldTag + " not found in case statement.");
        }
    }

    /** Get employe param by name */
    public String getParamByName(String paramName) {
        FieldTag fieldTag = FieldTag.valueOf(paramName);
        switch (fieldTag) {
        case fullName:
            return getFullName();
        case birthDate:
            return datetoString(getBirthDate(), getDateFormat());
        case hireDate:
            return datetoString(getHireDate(), getDateFormat());
        case position:
            return getPosition().toString();
        case description:
            return getDescription();
        case employeId:
            return getEmployeId().toString();
        case managerId:
            return getManagerId().toString();
        default:
            throw new IllegalStateException("Field tag " + fieldTag + " not found in case statement.");
        }
    }

}
